#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <sqlite3.h>
#import <UIKit/UIApplication.h>
#import <LockInfo/Plugin.h>

#import "framework/FWCommon.h"
#import "framework/FWApplication.h"
#import "framework/FWDBAccess.h"
#import "framework/FWPreferences.h"
#import "framework/FWPlugin.h"

#import "PICommon.h"
#import "PIIntegration.h"
#import "PIPlugin.h"
#import "PIContextObject.h"
#import "PITaskStatus.h"
#import "PIContext.h"

#import "PITaskIntegration202.h"
#import "PITask.h"


static NSInteger const kMaxSearchItems = 50;


@implementation PITaskIntegration202


// ******************************************************************************************
#pragma mark -
#pragma mark Fetching a list of Tasks

- (NSMutableString*)basicTaskListSQL:(FWApplication*)app prefs:(FWPreferences*)prefs contexts:(NSString*)contexts key:(NSString*)key parentKey:(NSString*)parentKey search:(NSString*)search
{
    PIPlugin* plugin = (PIPlugin*)app.fwPlugin;
    BOOL debug = plugin.isDebugMode;

    if (debug)
        XLog(@"LI:PITask: Building tasks SQL for prefs %@", prefs);
    
    NSMutableString* allSql = [NSMutableString stringWithCapacity:2000];
    NSInteger utcOffset = [[NSTimeZone systemTimeZone] secondsFromGMTForDate:[NSDate date]];

    [allSql appendFormat:
     @"select T.ZTITLE, T.ZSTARTDATEINTERNAL, T.ZSTARTDATEINCLUDESTIMEINTEGER, T.ZENDDATEINTERNAL, T.ZISALLDAY, T.ZPRIORITYINTEGER, T.ZFCPRIORITY, T.Z_PK, T.ZFLAGGEDINTEGER, T.ZACTIONINTEGER, T.ZPERCENTCOMPLETE, T.ZICON, T.ZHASRECURRENCEINTERNAL, P.ZTITLE, T.ZCHILDCOUNTINTERNAL, T.ZPARENTTASK, K.ZTITLE, K.ZICON, K.ZCOLOR, K.ZTIMEZONE,"
     " case when T.ZCONTEXT in (%@) then %i when T.ZCONTEXT is null then %i else %i end %@,"
     " M.Z_37TAGS %@,"
     " T.ZCHILDCOUNTINTERNAL,"
     " T.Z_OPT + K.Z_OPT,"
     " case T.ZPRIORITYINTEGER when 0 then %i else T.ZPRIORITYINTEGER end %@,"
     " case T.ZSTARTDATEINCLUDESTIMEINTEGER when 0 then T.ZSTARTDATEINTERNAL-(%d) else %T.ZSTARTDATEINTERNAL end %@," // If all day then date is UTC - we want local
     " case T.ZISALLDAY when 0 then T.ZENDDATEINTERNAL else T.ZENDDATEINTERNAL-(%d) end %@" // If all day then date is UTC - we want local
     " from ZPICALCALENDARITEM T"
     " left join ZPICALCALENDARITEM P on P.Z_PK=T.ZPROJECT"
     " left join ZPICALCALENDAR K on K.Z_PK=T.ZCALENDAR"
     " left join Z_15TAGS M on M.Z_15CALENDARITEMS1=T.Z_PK"
     " where T.Z_ENT=%@ AND T.ZCOMPLETEDDATE is null",
     contexts,
     ContextClassificationInContext,
     ContextClassificationNoContext,
     ContextClassificationOutOfContext,
     kFieldNameContextStatus,
     kFieldNameTagID,
     [prefs intValue:kPIOptionsMissingImportance withDefault:kPIOptionsMissingImportanceDefault],
     kFieldNameImportance,
     utcOffset,
     kFieldNameStartDate,
     utcOffset,
     kFieldNameDueDate,
     DBEntityValue(plugin, kPIEntityTypeTask)];

    
//    // Standard expessions
////    NSString* inbox = @"T.ZCONTEXT is null and T.ZPROJECT is null and T.ZACTIONINTEGER<=0";
//    NSString* inbox = @"1=1";
//
//    
//    //allSql = [allSql stringByAppendingString:@" and t.title not like '!!%'/*DEBUG*/"];
//    
//    // Don't return calendars in the ignore list
//    if (NSArray* calendars = [prefs arrayValue:kPIOptionsHiddenPIEventCalendars withDefault:nil]) {
//        NSMutableString* ignoreCalendars = [NSMutableString stringWithCapacity:calendars.count * 3];
//        for (NSString* calendar in calendars) {
//            [ignoreCalendars appendString:calendar withSeparator:@","];
//        }
//        if (ignoreCalendars.length > 0)
//            [allSql appendFormat:@" and T.ZCALENDAR not in (%@)/*IgnoreCals*/", ignoreCalendars];
//    }
//
//    
//    // Only generate filter terms if the option to show all tasks is off    
//    if (key) {
//        // Only want a single key
//        [allSql appendFormat:@" and T.Z_PK=%@/*KeyOnly*/", key];
//    }
//    else if (parentKey) {
//        // Only want children of a single key
//        [allSql appendFormat:@" and T.ZPARENTTASK=%@/*ChildrenOnly*/", parentKey];
//    }
//    else if (search) {
//        // Only want tasks that match a pattern   
//        search = [search stringByReplacingOccurrencesOfString:@"'" withString:@"''"];
//        search = [search stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
//        [allSql appendFormat:@" and T.ZTITLE like '%%%@%%'/*Search*/", search];
//    }
//    else if (![prefs boolValue:kPIOptionsShowAllTasks withDefault:kPIOptionsShowAllTasksDefault]) {
//        int taskFiling = [prefs intValue:kPIOptionsShowFiling withDefault:kPIOptionsShowFilingDefault];
//        if (taskFiling == TaskFilingInboxOnly)
//            // Only tasks without tags or a context or a project or an action
//            [allSql appendFormat:@" and %@/*ShowInboxOnly*/", inbox];
//        else if (taskFiling == TaskFilingFiledOnly)
//            // Don't allow tasks without tags or a context or a project or an action
//            [allSql appendFormat:@" and not(%@)/*ShowFiledOnly*/", inbox];
//        
//        if ([prefs boolValue:kPIOptionsHideDeferred withDefault:kPIOptionsHideDeferredDefault])
//            // Only allow tasks before Delegated
//            [allSql appendFormat:@" and T.ZACTIONINTEGER<%d/*HideDeferred*/", PIActionDelegated];
//        
//        // Get the date maxDays in the future
//        NSTimeInterval maxDays = (NSTimeInterval)[prefs intValue:kPIOptionsDayLimit withDefault:kPIOptionsDayLimitDefault];
//        NSTimeInterval seconds = maxDays * SECONDS_PER_DAY;
//        
//        // Get time of midnight today (ie past) in the UTC by building a new date from only the year, month
//        // and day components of the current date. PI start dates are always UTC
//        //NSDate *midnight = FWUTCForLocalDate(FWLocalDateForLocalMidnight());
//        NSDate *midnight = FWLocalDateForLocalMidnight();
//        if (debug)
//            XLog(@"LI:PITask: Local midnight utc date = %@", midnight);
//        
//        if ([prefs boolValue:kPIOptionsHideFuture withDefault:kPIOptionsHideFutureDefault]) {
//            // Only allow tasks starting before or at today's midnight or that don't have a start date
//            NSDate *tomorrow = [midnight addTimeInterval:SECONDS_PER_DAY];
//            [allSql appendFormat:@" and ifnull(%@,0)<%i/*HideFuture*/",
//             kFieldNameStartDate, (int)[tomorrow timeIntervalSinceReferenceDate]];
//        }
//        
//        // Get future midnight date by adding the number of days we will show (as seconds). Again this will be in UTC
//        NSDate* future = [midnight addTimeInterval:seconds];
//        if (debug)
//            XLog(@"LI:PITask: Cutoff days= %i; date = %@", (int)maxDays, future);
//        
////        // PI stores end times as local rather than UTC so we need to convert the local time to UTC
////        // in order to compare with an end date. This will be a hack as srictly we need the number of
////        // seconds offset on the given day to account for DST. That's the way that the display functions
////        // work.
////        NSInteger utcOffset = [[NSTimeZone systemTimeZone] secondsFromGMTForDate:midnight];
////        if (debug)
////            XLog(@"LI:PITask: UTC offset to local time = %i", utcOffset);
//        
//        // Only allow items with no duedate or a duedate before the threshold (not at as that would give 1 day too much)
//        [allSql appendFormat:@" and ifnull(%@,0)<%i/*MaxLimit-%i*/",
//         kFieldNameDueDate, (int)[future timeIntervalSinceReferenceDate], (int)maxDays];
//        
//        // Only return tags in the show list if we have one
//        if ([prefs boolValue:kPIOptionsNoTagsFilter withDefault:kPIOptionsNoTagsFilterDefault]) {
//            [allSql appendString:@" and M.Z_37TAGS is null/*SelectNoTags*/"];
//        }
//        else if (NSArray* tags = [prefs arrayValue:kPIOptionsTagsFilters withDefault:nil]) {
//            NSMutableString* selectTags = [NSMutableString stringWithCapacity:tags.count * 3];
//            for (NSString* tag in tags) {
//                [selectTags appendString:tag withSeparator:@","];
//            }
//            if (selectTags.length > 0)
//                [allSql appendFormat:@" and M.Z_37TAGS in (%@)/*SelectTags*/", selectTags];
//        }
//        
//        
//        int showParents = [prefs intValue:kPIOptionsShowParent withDefault:kPIOptionsShowParentDefault];
//        if (showParents == HideParentIfChildren)
//            // Only allow items that don't have children
//            [allSql appendString:@" and T.ZCHILDCOUNTINTERNAL=0/*HideWithChild*/"];
//        
//        if ([prefs boolValue:kPIOptionsHideUndated withDefault:kPIOptionsHideUndatedDefault])
//            // Only allow items that don't have a duedate
//            [allSql appendString:@" and T.ZENDDATEINTERNAL is not null/*HideUndated*/"];
//        
//        int actionType = [prefs intValue:kPIOptionsShowOnlyAction withDefault:kPIOptionsShowOnlyActionDefault];
//        if (actionType >= 0)
//            // Only allow items with the correct action
//            [allSql appendFormat:@" and T.ZACTIONINTEGER=%i/*OnlyAction*/", actionType];
//        
//        if ([prefs boolValue:kPIOptionsStarredOnly withDefault:kPIOptionsStarredOnlyDefault])
//            // Only allow items that have been flagged
//            [allSql appendString:@" and T.ZFLAGGEDINTEGER=1/*StarredOnly*/"];
//    }
//    else
//        [allSql appendString:@" /*ShowAllTasks*/"];
//    
//    [allSql appendString:@" group by T.Z_PK"];
//    
//    if (parentKey) {
//        // Add sorting terms
//        NSMutableString* sortExpr = [NSMutableString stringWithCapacity:200];
//        
//        [PITaskIntegration202 addSortField:sortExpr field:SortFieldOptionsReorder ascending:YES invert:NO prefs:prefs];
//        [PITaskIntegration202 addSortField:sortExpr field:SortFieldOptionsDueDate ascending:YES invert:NO prefs:prefs];
//        [allSql appendString:@" order by "];
//        [allSql appendString:sortExpr];
//    }
//    else if (!key) {
//        // Add sorting terms
//        NSMutableString* sortExpr = [NSMutableString stringWithCapacity:200];
//        
//        int sort1 = [prefs intValue:kPIOptionsSortField1 withDefault:kPIOptionsSortField1Default];
//        int sort2 = [prefs intValue:kPIOptionsSortField2 withDefault:kPIOptionsSortField2Default];
//        
//        // If we are using context filtering and neither of the sort settings is context then add a context sort
//        if ((((PITaskStatus*)app.info).isAutoContextOn || ((PITaskStatus*)app.info).isFixedContextOn) && (sort1 != SortFieldOptionsContext) && (sort2 != SortFieldOptionsContext))
//            [PITaskIntegration202 addSortField:sortExpr field:SortFieldOptionsContextFilter ascending:YES invert:NO prefs:prefs];
//        
//        BOOL sortAsc1 = [prefs boolValue:kPIOptionsSortField1Asc withDefault:kPIOptionsSortField1AscDefault];
//        int noneLast1 = [prefs intValue:kPIOptionsSortField1Order withDefault:kPIOptionsSortField1OrderDefault];
//        [PITaskIntegration202 addSortField:sortExpr field:sort1 ascending:sortAsc1 invert:(noneLast1 == SortFieldNoneOrderLast) prefs:prefs];
//        
//        BOOL sortAsc2 = [prefs boolValue:kPIOptionsSortField2Asc withDefault:kPIOptionsSortField2AscDefault];
//        int noneLast2 = [prefs intValue:kPIOptionsSortField2Order withDefault:kPIOptionsSortField2OrderDefault];
//        if (sort1 != sort2)
//            [PITaskIntegration202 addSortField:sortExpr field:sort2 ascending:sortAsc2 invert:(noneLast2 == SortFieldNoneOrderLast) prefs:prefs];
//        
//        if (sortExpr.length > 0) {
//            [allSql appendString:@" order by "];
//            [allSql appendString:sortExpr];
//        }
//        
//        // Restrict the number of items returned to the requested number		
//        int queryLimit = search ? kMaxSearchItems : [prefs intValue:kPIOptionsMaxTasks withDefault:kPIOptionsMaxTasksDefault];
//        [allSql appendFormat:@" limit %i", queryLimit];
//    }
    
    return allSql;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Augmenting a task - returns a new task

- (NSString*)augmentedBasicTaskSQL:(PITask*)task application:(FWApplication*)app
{
    NSString* sql = [NSString stringWithFormat:
                     @"select 2, N.ZNOTES, T.ZPERCENTCOMPLETE, C.ZTITLE, P.ZTITLE, T.ZCHILDCOUNTINTERNAL,"
                     " group_concat(M.Z_37TAGS, ','),"
                     " R.ZDAYSOFTHEMONTH, R.ZDAYSOFTHEWEEK, R.ZENDDATE, R.ZFIRSTDAYOFTHEWEEK, R.ZMONTHSOFTHEYEAR, R.ZOCCURRENCECOUNT, R.ZRECURRENCEINTERVAL, R.ZRECURRENCETYPEINTEGER, T.ZRECURRENCETYPEINTEGER, T.ZSENSITIVITY, null, null, null, null"
                     " from ZPICALCALENDARITEM T"
                     " left join ZPICALCALENDARITEM A on T.ZPARENTTASK=A.Z_PK"
                     " left join ZPICONTEXT C on T.ZCONTEXT=C.Z_PK"
                     " left join ZPICALCALENDARITEM P on T.ZPROJECT=P.Z_PK"
                     " left join Z_15TAGS M on M.Z_15CALENDARITEMS1=T.Z_PK"
                     " left join ZPICALRECURRENCERULE R on R.ZPARENTITEM=T.Z_PK"
                     " left join ZNOTESCONTAINER N on N.ZCALENDARITEM=T.Z_PK"
                     " where T.Z_PK = %@"
                     " group by T.Z_PK",
                     task.key];
    
    return sql;
        
}


- (NSString*)addAlarmDataToTaskListSQL:(FWApplication*)app keys:(NSString*)keys prefs:(FWPreferences*)prefs
{
    return nil;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Fetching a list of contexts

- (NSString*)numberOfLocationContextsSQL:(FWApplication *)application
{
    NSString* sql = @"select count(*) from ZPICONTEXT where ZLOCATIONNOTIFICATIONRADIUS > 0";
    
    return sql;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Fetching a list of contexts

- (NSString*)arrayOfContextsSQL:(FWApplication *)application earliest:(NSDate*)earliest counts:(BOOL)counts
{  
    if (counts) {
        PIPlugin* plugin = (PIPlugin*)application.fwPlugin;

        return [NSString stringWithFormat:
                @"select 2, C.Z_PK, C.ZTITLE, C.ZICON, C.ZLOCATIONNOTIFICATIONRADIUS, count(T.Z_PK)"
                " from ZPICONTEXT C"
                " left join ZPICALCALENDARITEM T on C.Z_PK=T.ZCONTEXT and T.ZCOMPLETEDDATE is null and T.Z_ENT=%@"
                "%@"
                " group by C.Z_PK"
                " order by C.ZTITLE",
                [plugin.dbEntityKeys objectForKey:kPIEntityTypeTask],
                earliest ? [NSString stringWithFormat:@" and ifnull(T.ZSTARTDATEINTERNAL,0)<=%i", (int)[earliest timeIntervalSinceReferenceDate]] : @""];

    }
    else {
        return [NSString stringWithString:
                @"select 2, C.Z_PK, C.ZTITLE, C.ZICON, C.ZLOCATIONNOTIFICATIONRADIUS, -1"
                " from ZPICONTEXT C"
                " order by C.ZTITLE"];
    }
}


// ******************************************************************************************
#pragma mark -
#pragma mark URL to create a new task

- (NSURL*)urlForNewTask:(NSString*)title importance:(enum PIImportanceType)importance application:(FWApplication*)app info:(NSArray*)info
{
    NSString* encodedTitle = FWXMLEncodeString(title);
    
    // Get the highest current task id
    NSNumber* maxKey = [info objectAtIndex:0];
    NSString* uuid = [info objectAtIndex:1];
    NSString* inbox = FWXMLEncodeString([info objectAtIndex:2]);
    NSString* calendar = FWXMLEncodeString([info objectAtIndex:3]);
    
    NSDateFormatter *format = [[NSDateFormatter alloc] init];
    [format setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [format setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
    NSString* currentDate = [format stringFromDate:[NSDate date]]; 
    [format release];
    
    NSString* importanceCode;
    switch (importance) {
        case PIImportanceTop:
            importanceCode = @"3"; //none
            break;
        case PIImportanceHigh:
            importanceCode = @"2"; //ok
            break;
        case PIImportanceNormal:
            importanceCode = @"1"; //med
            break;
        case PIImportanceLow:
            importanceCode = @"0"; //low
            break;
        default:
            importanceCode = @""; //low
            break;
    }
    
//<data><task id="x-coredata://66230855-3499-48F6-B36C-3243C0831871/PICalTask/p244"><modified>2001-01-01T00:00:00Z</modified><startdate/><duedate/><projectName>Inbox</projectName><teamtask>NO</teamtask><completed>NO</completed><percentcomplete>0</percentcomplete><priority>0</priority><priorityandrank/><sensitivity>0</sensitivity><status>0</status><title>!!Action - none - icon</title><note/><color>#000000</color><categories>Internal Task Calendar</categories><repeat>0</repeat><recurrence><data></data></recurrence></task></data>    
    
    CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
    NSString* newUUID = [(NSString*)CFUUIDCreateString(kCFAllocatorDefault, uuidRef) autorelease];
    CFRelease(uuidRef);
    
    NSString* params = [NSString stringWithFormat:@"<data>"
                        "<task id=\"x-coredata://%@/PIEventTask/p%d\">"
                        "<modified>%@</modified>"
                        "<startdate>%@</startdate>"
                        "<duedate/>"
                        "<projectName>%@</projectName>"
                        "<teamtask>NO</teamtask>"
                        "<completed>NO</completed>"
                        "<percentcomplete>0</percentcomplete>"
                        "<priority>%@</priority>"
                        "<priorityandrank/>"
                        "<sensitivity>0</sensitivity>"
                        "<status>0</status>"
                        "<title>%@</title>"
                        "<note/>"
                        "<color>#000000</color>"
                        "<categories>%@</categories>"
                        "<repeat>0</repeat>"
                        "<recurrence><data></data></recurrence>"
                        "</task>"
                        "</data>",
                        newUUID,
                        [maxKey intValue] + 1,
                        currentDate,
                        currentDate,
                        inbox,
                        importanceCode,
                        encodedTitle,
                        calendar];
    
    NSString* url = [NSString stringWithFormat:@"informant://createTask?%@",
                     FWBase64EncodeString(params)];
    
    return [NSURL URLWithString:url];
}

- (NSString*)infoForNewTaskSQL:(FWApplication*)app
{
    PIPlugin* plugin = (PIPlugin*)app.fwPlugin;

    NSString* sql = [NSString stringWithFormat:@"select 2, (select max(Z_PK) from ZPICALCALENDARITEM),"
                     " Z_UUID,"
                     " (select ZTITLE from ZPICALCALENDARITEM where Z_ENT=%@),"
                     " (select ZTITLE from ZPICALCALENDAR where Z_ENT=%@ and ZINTERNALID=%d)"
                     " from Z_METADATA"
                     " where Z_VERSION = (select max(Z_VERSION) from Z_METADATA)",
                     [plugin.dbEntityKeys objectForKey:kPIEntityTypeProjectInternal],
                     [plugin.dbEntityKeys objectForKey:kPIEntityTypeCalendarInternal],
                     kPIEntityIDInternalTaskCalendar]; 
    
    return sql;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Completing a task

- (NSString*)completeTaskSQL:(PITask*)task application:(FWApplication*)app
{
    NSString* sql = [NSString stringWithFormat:
                     @"update ZPICALCALENDARITEM"
                     " set ZCOMPLETEDDATE=%i"
                     " where Z_PK=%@",
                     (int)[[NSDate date] timeIntervalSinceReferenceDate],
                     task.key];
    
    return sql;
}

@end
